//
//  RFATree.m
//  RFA Manager
//
//  Created by Torsten Kammer on 23.02.08.
//  Copyright 2008 Ferroequinologist.de. All rights reserved.
//

#import "RFATreeNode.h"
#import "RFATreeEnumerator.h"

#import "NSArrayRFAAdditions.h"
#import "NSStringRFAAdditions.h"
#import "PropertyCategories.h"

#import "OpenDDSFile.h"
#import "DDSDrawToImage.h"

@interface RFATreeNode ()

- (id)initWithData:(NSData *)data name:(NSString *)name parent:(RFATreeNode *)node;

@end

@implementation RFATreeNode

+ (NSSet *)keyPathsForValuesAffectingContents
{
	return [NSSet setWithObjects:@"data", @"isText", nil];
}

+ (NSSet *)keyPathsForValuesAffectingImage
{
	return [NSSet setWithObjects:@"data", @"isImage", nil];
}

+ (NSSet *)keyPathsForValuesAffectingIsImage
{
	return [NSSet setWithObject:@"name"];
}

+ (NSSet *)keyPathsForValuesAffectingIsText
{
	return [NSSet setWithObject:@"name"];
}

+ (NSSet *)keyPathsForValuesAffectingFullPath
{
	return [NSSet setWithObjects:@"parent.fullPath", @"name", nil];
}

+ (NSSet *)keyPathsForValuesAffectingFullSize
{
	return [NSSet setWithObjects:@"children.fullSize", @"data", nil];
}

+ (NSSet *)keyPathsForValuesAffectingFlattenedTree
{
	return [NSSet setWithObjects:@"children", @"children.flattenedTree", nil];
}

+ (RFATreeNode *)nodeFromFile:(NSString *)file parent:(RFATreeNode *)node;
{
	BOOL isDirectory;
	if (![[NSFileManager defaultManager] fileExistsAtPath:file isDirectory:&isDirectory]) return nil;
	if (isDirectory)
	{
		RFATreeNode *result = [[self alloc] initAsDirectory:file.lastPathComponent parent:node];
		for (NSString *path in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:file error:NULL])
			[[result mutableArrayValueForKey:@"children"] addObject:[self nodeFromFile:[file stringByAppendingPathComponent:path] parent:result]];
		return result;
	}
	else
		return [[self alloc] initWithData:[NSData dataWithContentsOfFile:file] name:file.lastPathComponent parent:node];
}

- (id)copyWithZone:(NSZone *)zone
{
    RFATreeNode *result = [[self class] allocWithZone:zone];
    result->name = [name copyWithZone:zone];
    result->data = [data copyWithZone:zone];
    result->children = [NSMutableArray array];
	for (RFATreeNode *child in [self valueForKey:@"children"])
		[result->children addObject:[child copyWithZone:zone]];
	
	result.undoManager = self.undoManager;
    return result;
}

- (id)init
{
	return [self initAsDirectory:@"bf1942"];
}

- (id)initAsDirectory:(NSString *)directory;
{
    return [self initAsDirectory:directory parent:nil];
}

- (id)initAsDirectory:(NSString *)directory parent:(RFATreeNode *)node;
{
    if (![super init]) return nil;
    
	parent = node;
    name = [directory copy];
    data = nil;
    children = [NSMutableArray array];
	
	if (parent.undoManager) self.undoManager = parent.undoManager;
    
    return self;
}

- (id)initWithData:(NSData *)someData name:(NSString *)aName parent:(RFATreeNode *)node;
{
    if (![super init]) return nil;
    
	parent = node;
    name = [aName copy];
    data = [someData copy];
    children = nil;
	
	if (parent.undoManager) self.undoManager = parent.undoManager;
	[self addObserver:self forKeyPath:@"data" options:NSKeyValueObservingOptionNew context:@"NSData"];
    
    return self;
}

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (context == undoManager)
	{
		[undoManager registerUndoWithTarget:self selector:@selector(setName:) object:[change objectForKey:NSKeyValueChangeOldKey]];
	}
	else if (context == @"NSData")
	{
		image = nil;
	}
	else
		[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}


- (void)addFileWithData:(NSData *)someData atPath:(NSString *)path;
{
	[self addFileWithData:someData atPathArray:path.pathComponents];
}

- (void)addFileWithData:(NSData *)someData atPathArray:(NSArray *)path;
{
	if (data) return;
	
	if (path.count == 1) // Create data child for it
		[[self mutableArrayValueForKey:@"children"] addObject:[[RFATreeNode alloc] initWithData:someData name:path.lastObject parent:self]];
	else
	{
		RFATreeNode *directory = nil;
		// Find a directory, or create one.
		for (RFATreeNode *child in [self valueForKey:@"children"])
		{
			if ([child.name isEqual:path.firstObject] && child.data == nil)
			{
				directory = child;
				break;
			}
		}
		if (!directory)
		{
			directory = [[RFATreeNode alloc] initAsDirectory:path.firstObject parent:self];
			[[self mutableArrayValueForKey:@"children"] addObject:directory];
		}
		[directory addFileWithData:someData atPathArray:path.arrayByRemovingFirstObject];
	}
}

- (RFATreeNode *)childWithName:(NSString *)aName;
{
	for (RFATreeNode *child in [self valueForKey:@"children"])
		if ([child.name isEqual:aName])
			return child;
	
	return nil;
}
- (RFATreeNode *)childWithPathArray:(NSArray *)pathArray
{
	if (pathArray.count == 1) return [self childWithName:pathArray.firstObject];
	return [[self childWithName:pathArray.firstObject] childWithPathArray:pathArray.arrayByRemovingFirstObject];
}

@dynamic flattenedTree;
- (NSArray *)flattenedTree;
{
	NSMutableArray *result = [NSMutableArray arrayWithObject:self];
	[result addObjectsFromArray:[self valueForKeyPath:@"children.flattenedTree"]];
	
	return [result copy];
}

- (BOOL)hasChildWithName:(NSString *)aName isDirectory:(BOOL *)isDir;
{
	RFATreeNode *child = [self childWithName:aName];
	if (!child) return NO;
	if (isDir) *isDir = !child.isLeaf;
	return YES;
}

@synthesize data, name, parent;

- (NSUInteger)countOfChildren;
{
	return children.count;
}
- (RFATreeNode *)objectInChildrenAtIndex:(NSUInteger)index;
{
	return [children objectAtIndex:index];
}
- (void)insertObject:(RFATreeNode *)node inChildrenAtIndex:(NSUInteger)index;
{
	[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:[NSIndexSet indexSetWithIndex:index] forKey:@"children"];
	[children insertObject:node atIndex:index];
	[[undoManager prepareWithInvocationTarget:self] removeObjectFromChildrenAtIndex:index];
	node.undoManager = self.undoManager;
	node.parent = self;
	[self didChange:NSKeyValueChangeInsertion valuesAtIndexes:[NSIndexSet indexSetWithIndex:index] forKey:@"children"];
}
- (void)removeObjectFromChildrenAtIndex:(NSUInteger)index;
{
	[self willChange:NSKeyValueChangeRemoval valuesAtIndexes:[NSIndexSet indexSetWithIndex:index] forKey:@"children"];
	[[undoManager prepareWithInvocationTarget:self] insertObject:[self objectInChildrenAtIndex:index] inChildrenAtIndex:index];
	[children removeObjectAtIndex:index];
	[self didChange:NSKeyValueChangeRemoval valuesAtIndexes:[NSIndexSet indexSetWithIndex:index] forKey:@"children"];
}

- (void)removeChildNamed:(NSString *)aName
{
	NSMutableArray *childrenFound = [NSMutableArray array];
	for (RFATreeNode *child in [self valueForKey:@"children"])
	{
		if ([child.name isEqual:aName])
			[childrenFound addObject:child];
	}
	[[self mutableArrayValueForKey:@"children"] removeObjectsInArray:childrenFound];
}

@dynamic contents;
- (NSString *)contents
{
	if (!self.isText) return nil;
	return [NSString stringWithData:self.data encoding:NSISOLatin1StringEncoding];
}
- (void)setContents:(NSString *)newContents
{
	if (!self.isText) return;
    self.data = [newContents dataUsingEncoding:NSISOLatin1StringEncoding];
}

@dynamic image;
- (NSImage *)image
{
	if (!self.isImage) return nil;
	if (image) return image;
	
	BOOL needsToSwap = NO;
    NSBitmapImageRep *bitmapImage;
    if ([self.name.pathExtension isEqual:@"dds"])
    {
        DDSFile *file = DDSOpenData((CFDataRef) self.data);
        if (!file) return nil;
        CFIndex width = DDSGetWidth(file);
        CFIndex height = DDSGetHeight(file);
        CGImageRef cgImage = DDSCreateImage(file, width, height);
        DDSDestroy(file);
        if (!cgImage) return nil;
        bitmapImage = [[NSBitmapImageRep alloc] initWithCGImage:cgImage];
        CGImageRelease(cgImage);
    }
    else if ([self.name.pathExtension isEqual:@"raw"])
    {
        BOOL is16Bit = YES;
        NSInteger edgeLength = (NSInteger) sqrtf((float) (self.data.length/2));
        if (edgeLength * edgeLength * 2 < self.data.length)
        {
            edgeLength = (NSInteger) sqrtf((float) (self.data.length));
            is16Bit = NO;
        }
        
        CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef) self.data);
        if (!provider) return nil;
        CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceGray();
        CGImageRef cgImage = CGImageCreate(edgeLength, edgeLength, is16Bit ? 16 : 8, is16Bit ? 16 : 8, is16Bit ? 2*edgeLength : edgeLength, colorspace, is16Bit ? kCGBitmapByteOrder16Little : 0, provider, NULL, YES, kCGRenderingIntentDefault);
        CGColorSpaceRelease(colorspace);
        CGDataProviderRelease(provider);
        if (!cgImage) return nil;
        bitmapImage = [[NSBitmapImageRep alloc] initWithCGImage:cgImage];
        CGImageRelease(cgImage);
        needsToSwap = YES;
    }
    else
    {
        bitmapImage = [[NSBitmapImageRep alloc] initWithData:self.data];
    }
    
    if (!bitmapImage) return nil;
    
    image = [[NSImage alloc] initWithSize:bitmapImage.size];
    [image addRepresentation:bitmapImage];
	image.flipped = needsToSwap;
    
	return image;
}

- (NSArray *)nodesMatchingPredicate:(NSPredicate *)predicate
{
	NSMutableArray *result = [NSMutableArray array];
	if ([predicate evaluateWithObject:self]) [result addObject:self];
	for (RFATreeNode *child in children)
		[result addObjectsFromArray:[child nodesMatchingPredicate:predicate]];
	
	return [result copy];
}

@dynamic fullPath;
- (NSString *)fullPath
{
	if (!parent) return self.isLeaf ? self.name : [self.name stringByAppendingString:@"/"];
	
	if (data) return [parent.fullPath stringByAppendingPathComponent:self.name];
	else return [[parent.fullPath stringByAppendingPathComponent:self.name] stringByAppendingString:@"/"];
}

@dynamic isText;
- (BOOL)isText;
{
	if (!data) return NO;
	return [[NSArray arrayWithObjects:@"con", @"rs", @"ssc", @"txt", nil] containsObject:self.name.pathExtension];
}

@dynamic isImage;
- (BOOL)isImage;
{
	if (!data) return NO;
	return [[NSArray arrayWithObjects:@"dds", @"tga", @"jpg", @"bmp", @"raw", @"png", nil] containsObject:self.name.pathExtension];
}

@dynamic fullSize;
- (NSUInteger)fullSize;
{
	if (data) return data.length;
	else return [[self valueForKeyPath:@"children.@sum.fullSize"] unsignedIntegerValue];
}

- (NSEnumerator *)leafEnumerator;
{
	return [[RFATreeEnumerator alloc] initWithNode:self];
}

@dynamic isLeaf;
- (BOOL)isLeaf
{
	return self.data != nil;
}

@dynamic leafCount;
- (NSUInteger)leafCount
{
	if (data) return 1;
	return [[self valueForKeyPath:@"children.@sum.leafCount"] unsignedIntegerValue];
}

@dynamic undoManager;
- (void)setUndoManager:(NSUndoManager *)manager;
{
	[self willChangeValueForKey:@"undoManager"];
	undoManager = manager;
	if (undoManager) [self addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld context:undoManager];
	[self didChangeValueForKey:@"undoManager"];
	for (RFATreeNode *child in [self valueForKey:@"children"])
		child.undoManager = manager;
}
- (NSUndoManager *)undoManager
{
	return undoManager;
}

- (BOOL)isDescendantOf:(RFATreeNode *)node;
{
	if (!self.parent) return NO;
	if (self.parent == node) return YES;
	return [self.parent isDescendantOf:node];
}

- (NSFileWrapper *)fileWrapper;
{
	if (self.isLeaf)
	{
		NSFileWrapper *result = [[NSFileWrapper alloc] initRegularFileWithContents:self.data];
		result.preferredFilename = self.name;
		return result;
	}
	else
	{
		NSFileWrapper *result = [[NSFileWrapper alloc] initDirectoryWithFileWrappers:[NSDictionary dictionaryWithObjects:[self valueForKeyPath:@"children.fileWrapper"] forKeys:[self valueForKeyPath:@"children.name"]]];
		result.preferredFilename = self.name;
		return result;
	}
}

@end
